import math
import numpy as np
import thread
from scipy import signal

numthreads = 0
AllTools = []

# Decorator to automatically construct an instance of the tool and put it in AllTools
def RegisterTool(c):
    AllTools.append(c())
    return c

# Returns the value at a given point
def BrushInterpolate(i, start, end, feather, value):
    if i < start:
        v = value * (1.0 - ((start - i) / float(feather)))
        v = math.cos(v*math.pi) * -0.5 + 0.5
    elif i > end:
        v = value * (1.0 - ((i - end) / float(feather)))
        v = math.cos(v*math.pi) * -0.5 + 0.5
    else:
        v = value
    return v

# Gets a list of samples
def GetBrushMap(brushSize, feather):
    return map(lambda x:BrushInterpolate(x, feather, brushSize * 2 + feather, feather, 1.0), range(brushSize * 2+feather*2))

# Base class for all tools.
class BrushTool:
    name = "default"
    icon = ""
    
    def __init__(self):
        self.cached = None
        
    def Cache(self, sample):
        pass

    def ApplyThreaded(self, original, wave, start, end, feather, value, brushMap, channelIndex):
       thread.start_new_thread(self.Apply, (original, wave, start, end, feather, value, brushMap, channelIndex))

    def Apply(self, original, wave, start, end, feather, value, brushMap, channelIndex):
        global numthreads
        numthreads += 1
        # Chop stuff based on the start and end of the editable section
        s = max(start - feather, 0)
        lc = max(-(start - feather), 0)
        e = min(end + feather, len(wave))
        rc = max((end + feather) - len(wave), 0)
        if lc:
            brushMap = brushMap[lc:]
        if rc:
            brushMap = brushMap[:-rc]

        subArray = wave[s:e]
        valueMap = np.multiply(brushMap, value)

        wave[s:e] = self.Operate(subArray, valueMap)
        numthreads -= 1

    def Operate(self, subArray, valueMap):
        pass


@RegisterTool
class AmplifyTool(BrushTool):
    name = "Increase Amplitude"
    icon = "images/tool_increase_amplitude.png"

    def Operate(self, subArray, valueMap):
        mulMap = np.add(1, valueMap * 0.5)
        return np.clip(np.multiply(subArray, mulMap), -1, 1)

@RegisterTool
class ReduceTool(BrushTool):
    name = "Reduce Amplitude"
    icon = "images/tool_decrease_amplitude.png"

    def Operate(self, subArray, valueMap):
        mulMap = np.add(1, np.negative(valueMap * 0.5))
        return np.multiply(subArray, mulMap)

@RegisterTool
class RestoreTool(BrushTool):
    name = "Restore To Original"
    icon = "images/tool_restore.png"

    def Apply(self, original, wave, start, end, feather, value, brushMap, channelIndex):
        s = max(start - feather, 0)
        lc = max(-(start - feather), 0)
        e = min(end + feather, len(wave))
        rc = max((end + feather) - len(wave), 0)
        if lc:
            brushMap = brushMap[lc:]
        if rc:
            brushMap = brushMap[:-rc]
        subArray1 = wave[s:e]
        subArray2 = original[s:e]
        valueMap = np.multiply(brushMap, value)

        subArray1 = np.multiply(subArray1, np.add(np.negative(valueMap), 1))
        subArray2 = np.multiply(subArray2, valueMap)
        wave[s:e] = np.add(subArray1, subArray2)

@RegisterTool
class CrushTool(BrushTool):
    name = "Clip / Distort"
    icon = "images/tool_crush.png"

    def Operate(self, subArray, valueMap):
        target = np.sum(np.absolute(subArray)) / len(subArray)
        targetArray = np.multiply(np.sign(subArray), target)
        subArray1 = np.multiply(subArray, np.add(np.negative(valueMap), 1))
        subArray2 = np.multiply(valueMap, targetArray)
        return np.add(subArray1, subArray2)

@RegisterTool
class LowPassTool(BrushTool):
    name = "Low-Pass"
    icon = "images/tool_lowpass.png"

    def __init__(self):
        BrushTool.__init__(self)
        self.tf = 1000
        self.low = signal.firwin(self.tf, cutoff = 0.01, window = "hamming")
        self.high = -signal.firwin(self.tf, cutoff = 0.01, window = "hamming")
        self.high[self.tf/2] = self.high[self.tf/2] + 1
        self.bp = (self.low + self.high)
        self.bp[self.tf/2] = self.bp[self.tf/2] + 1

    def Cache(self, sample):
        if self.cached is None:
            self.cached = [signal.lfilter(self.low, 1, s) for s in sample]

    def Apply(self, original, wave, start, end, feather, value, brushMap, channelIndex):
        s = max(start - feather, 0)
        lc = max(-(start - feather), 0)
        e = min(end + feather, len(wave))
        rc = max((end + feather) - len(wave), 0)
        if lc:
            brushMap = brushMap[lc:]
        if rc:
            brushMap = brushMap[:-rc]
        subArray1 = wave[s:e]
        subArray2 = self.cached[channelIndex][s:e]
        valueMap = np.multiply(brushMap, value)

        subArray1 = np.multiply(subArray1, np.add(np.negative(valueMap), 1))
        subArray2 = np.multiply(subArray2, valueMap)
        wave[s:e] = np.add(subArray1, subArray2)
